package cn.wzc.crystal.platform.query.impl;

import cn.wzc.crystal.platform.query.QuerySql;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PrepareSqlLine {

	private static final String conditionRegex = "#\\?(?<name>\\w+)[?!]?\\s+";
	private static final Pattern conditionPattern = Pattern.compile(conditionRegex);
	private static final Pattern parameterPattern = Pattern.compile(":(?<name>\\w+)");

	/**
	 * sql 行
	 */
	private final String sql;

	/**
	 * 普通 sql 行
	 */
	private final boolean planSql;

	/**
	 * 成立条件
	 */
	private final List<String> conditions;

	/**
	 * 编译处理参数
	 */
	private final List<PrepareSqlParts> parts;

	public PrepareSqlLine(String sql) {
		this.planSql = !sql.startsWith("#");
		this.sql = this.cleanSql(sql);
		this.conditions = this.guessConditions(sql);
		this.parts = this.splitParts();
	}


	/**
	 * 编译行
	 *
	 * @param context 编译参数
	 * @return 编译好的行
	 */
	public QuerySql compile(Map<String, Object> context) {
		// 普通 sql 直接返回
		if (this.isPlanSql()) {
			return new QuerySql(this.sql, new Object[0]);
		}

		// 检查条件
		if (!checkCondition(context)) {
			return null;
		}

		// 检查站位符
		for (PrepareSqlParts part : this.parts) {
			if (part.isPlaceholder()) {
				if (!context.containsKey(part.getPlaceholder())) {
					return null;
				}
			}
		}

		// 执行真实编译
		final List<Object> args = new ArrayList<>();
		final StringBuilder sql = new StringBuilder();
		for (PrepareSqlParts part : this.parts) {
			if (part.isPlan()) {
				sql.append(part.getPart()).append(" ");
				continue;
			}
			if (part.isPlaceholder()) {
				String placeholder = part.getPlaceholder();
				Object value = context.get(placeholder);
				String replaceValue = "?";
				if (isCollection(value)) {
					replaceValue = fillCollection(args, value);
				} else if (isArray(value)) {
					replaceValue = fillCollection(args, arrayToList(value));
				} else {
					args.add(value);
				}
				sql.append(replaceValue);
				// 右边有括号
				if (part.isRightParent()) {
					sql.append(")");
				}
				sql.append(" ");
			}

		}

		return new QuerySql(sql.toString().trim(), args.toArray());
	}

	private boolean checkCondition(Map<String, Object> context) {
		if (conditions.isEmpty()) {
			return true;
		}
		for (String condition : conditions) {
			Object value = context.get(condition);
			if (null == value) {
				return false;
			}
			if (value instanceof Boolean) {
				if ((boolean) value) {
					return true;
				}
			}
		}
		return false;
	}

	private List<PrepareSqlParts> splitParts() {
		List<PrepareSqlParts> parts = new ArrayList<>();
		for (String part : sql.split("\\s")) {
			if (StringUtils.isNotBlank(part)) {
				parts.add(new PrepareSqlParts(part));
			}
		}
		return parts;
	}

	/**
	 * 推测处理条件
	 *
	 * @param sql 推测的 sql
	 * @return 处理条件, 如果没有找到, 则返回 null
	 */
	private List<String> guessConditions(String sql) {
		final List<String> conditions = new ArrayList<>();
		final Matcher cndMatcher = conditionPattern.matcher(sql);
		while (cndMatcher.find()) {
			String name = cndMatcher.group("name");
			if (StringUtils.isNotBlank(name)) {
				conditions.add(name);
			}
		}
		return conditions;
	}

	private String cleanSql(String sql) {
		if (sql.startsWith("#?")) {
			return sql.replaceAll(conditionRegex, "").replace("#?", "");
		}
		return sql;
	}

	private Object arrayToList(Object value) {
		return Arrays.asList(((Object[]) value));
	}

	private String fillCollection(List<Object> args, Object value) {
		StringBuilder sqlPartial = new StringBuilder("(");
		for (Object val : (Collection<?>) value) {
			sqlPartial.append("?,");
			args.add(val);
		}
		sqlPartial = new StringBuilder(new StringBuilder(sqlPartial.substring(0, sqlPartial.length() - 1) + ")"));
		return sqlPartial.toString();
	}

	private boolean isCollection(Object value) {
		return Collection.class.isAssignableFrom(value.getClass());
	}

	private boolean isArray(Object value) {
		return value.getClass().isArray();
	}


	/**
	 * 是否是预编译 SQL
	 *
	 * @return true 表是
	 */
	public boolean isPrepareSql() {
		return !this.planSql;
	}

	/**
	 * 是否是普通 SQL
	 *
	 * @return true 表是
	 */
	public boolean isPlanSql() {
		return this.planSql;
	}
}
